"""
@project: vcat
@author: kang 
@github: https://github.com/fsksf 
@since: 2021/12/22 10:13 PM
"""
from typing import Callable
from vcat.core.constant import DataLevel
from vcat.core.obj import BarData, TickData


class BarGenerator:
    """
    For:
    1. generating 1 minute bar data from tick data
    2. generateing x minute bar/x hour bar data from 1 minute data

    Notice:
    1. for x minute bar, x must be able to divide 60: 2, 3, 5, 6, 10, 15, 20, 30
    2. for x hour bar, x can be any number
    """

    def __init__(
        self,
        on_bar: Callable,
        symbol: str,
        window: int = 1,
        bar_level: DataLevel = DataLevel.minute,
    ):
        """Constructor"""
        self.bar: BarData = None
        self.on_bar: Callable = on_bar
        self.symbol = symbol

        self.bar_level: DataLevel = bar_level
        self.interval_count: int = 0

        self.window: int = window

        self.last_tick: TickData = None
        self.last_bar: BarData = None

    def update_tick(self, tick: TickData) -> None:
        """
        Update new tick data into generator.
        """
        new_minute = False

        # Filter tick data with 0 last price
        if not tick.last_price:
            return

        # Filter tick data with older timestamp
        if self.last_tick and tick.dt < self.last_tick.dt:
            return

        if not self.bar:
            new_minute = True
        elif (
            (self.bar.dt.minute != tick.dt.minute)
            or (self.bar.dt.hour != tick.dt.hour)
        ):
            self.bar.dt = self.bar.dt.replace(
                second=0, microsecond=0
            )
            self.on_bar(self.bar)

            new_minute = True

        if new_minute:
            self.bar = BarData(
                code=tick.code,
                broker_name=tick.broker_name,
                interval=self.window,
                dt=tick.dt,
                open_price=tick.last_price,
                high_price=tick.last_price,
                low_price=tick.last_price,
                close_price=tick.last_price,
                volume=tick.volume,
                level=self.bar_level,
                contract_type=tick.contract_type,
                exchange=tick.exchange,
            )
        else:
            self.bar.high_price = max(self.bar.high_price, tick.last_price)
            if tick.high_price > self.last_tick.high_price:
                self.bar.high_price = max(self.bar.high_price, tick.high_price)

            self.bar.low_price = min(self.bar.low_price, tick.last_price)
            if tick.low_price < self.last_tick.low_price:
                self.bar.low_price = min(self.bar.low_price, tick.low_price)

            self.bar.close_price = tick.last_price
            self.bar.open_interest = tick.open_interest
            self.bar.dt = tick.dt

        if self.last_tick:
            volume_change = tick.volume - self.last_tick.volume
            self.bar.volume += max(volume_change, 0)

        self.last_tick = tick

    def update_bar(self, bar: BarData) -> None:
        """
        更新bar并生成下一级别bar,
        所传bar的window必须为1
        """
        if bar.interval != 1:
            return
        # If not inited, create window bar object
        if not self.bar:
            dt = bar.dt.replace(second=0, microsecond=0)
            self.bar = BarData(
                code=bar.code,
                broker_name=bar.broker_name,
                dt=dt,
                open_price=bar.open_price,
                high_price=bar.high_price,
                low_price=bar.low_price,
                close_price=bar.close_price,
                volume=bar.volume,
                level=self.bar_level,
                interval=self.window,
                contract_type=bar.contract_type,
                exchange=bar.exchange
            )
        else:
            self.bar.high_price = max(
                self.bar.high_price,
                bar.high_price
            )
            self.bar.low_price = min(
                self.bar.low_price,
                bar.low_price
            )

        # Update close price/volume into window bar
        self.bar.close_price = bar.close_price
        self.bar.volume += int(bar.volume)
        mature_bar = None
        # Check if window bar completed
        if self.bar_level == DataLevel.minute and (bar.dt.minute + 1) % self.window == 0:
            self.bar.dt = self.bar.dt.replace(second=0, minute=0)
            mature_bar = self.bar
            self.bar = None
        elif self.bar_level == DataLevel.hour and (bar.dt.hour + 1) % self.window == 0:
            self.bar.dt = self.bar.dt.replace(minute=0, second=0, microsecond=0)
            mature_bar = self.bar
            self.bar = None
        elif self.bar_level == DataLevel.day and (bar.dt.day + 1) % self.window == 0:
            self.bar.dt = self.bar.dt.replace(hour=0, minute=0, second=0, microsecond=0)
            mature_bar = self.bar
            self.bar = None
        if mature_bar:
            self.on_bar(mature_bar)
        self.last_bar = bar


